<?php

namespace pay\wechat;

use WeChatPay\Builder;
use WeChatPay\ClientJsonTrait;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;

class WxPay2
{

    static $merchantPrivateKeyInstance;   // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名

    /**
     * 初始化
     * @return array [$instance,$wxPayConfig];
     * @author wzb
     * @date 2023/8/8 11:05
     */
    public static function init()
    {
        // 设置参数
        $wxPayConfig = config('wx_pay_config');
        $mchId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $serial = $wxPayConfig['cert_serial'] ?? '';   // 「商户API证书」的「证书序列号」
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId;
        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem';
        self::$merchantPrivateKeyInstance = $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        // 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
        $platformCertificateFilePath = $certPath . '/cert.pem';
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid' => $mchId,
            'serial' => $serial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);
        return [$instance, $wxPayConfig];
    }

    /**
     * 支付
     * @param $payData
     * @param $notifyUrl
     * @return array
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function pay($payData = [], $notifyUrl = '')
    {
        $notifyUrl = $notifyUrl ?? (config('host_api') . '/v1/notify/wx_notify');
        [$instance, $wxPayConfig] = self::init(); // 赋值给变量
        $merchantId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $appid = $wxPayConfig['appid'] ?? '';     // 商户号
        // 调用下单
        $payJsonData = [
            'mchid' => $merchantId,
            'appid' => $appid,
            'out_trade_no' => $payData['out_trade_no'] ?? 0,
            'description' => $payData['description'] ?? 0,
            'notify_url' => $notifyUrl,
            'amount' => [
                'total' => $payData['money'] ?? 0,  // 分
                'currency' => 'CNY'
            ],
        ];
        try {
            $resp = $instance
                ->chain('v3/pay/transactions/app')
                ->post(['json' => $payJsonData]);

            if ($resp->getStatusCode() == 200) {
                $wo = json_decode($resp->getBody(), true); //prepay_id
                $params = [
                    'partnerId' => $appid,
                    'timeStamp' => (string)Formatter::timestamp(),
                    'nonceStr' => Formatter::nonce(),
                    'prepayId' => $wo["prepay_id"],
                ];
                $params += ['sign' => Rsa::sign(
                    Formatter::joinedByLineFeed(...array_values($params)),
                    self::$merchantPrivateKeyInstance
                )];
                return [1, $params];
            } else {
                return [0, '微信支付失败'];
            }
        } catch (\Exception $e) {
            $code = $e->getCode();
            $data = '';
            // 进行错误处理
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $data = json_decode($r->getBody(), true);
                writeLog("wx_pay_error". json_encode($data), "wx_pay");
            }
        }
        return [$code, $data];
        // end
    }

    /**
     * 退款
     * @param $orderSn
     * @param $tradeNo
     * @param $refundMoney
     * @return array|mixed array [$code,$data] $code=1 成功
     * @author wzb
     * @date 2023/8/8 9:09
     */
    static function refundOrder($orderSn, $tradeNo, $refundMoney)
    {
        [$instance, $wxPayConfig] = self::init();
        try {
            $response = $instance
                ->chain('v3/refund/domestic/refunds')
                ->post([
                    'json' => [
                        'transaction_id' => $tradeNo,
                        'out_refund_no' => $orderSn,
                        'amount' => [
                            'refund' => $refundMoney,
                            'total' => $refundMoney,
                            'currency' => 'CNY',
                        ],
                    ],
                ]);
            // 正常逻辑回调处理
            $code = $response->getStatusCode();
            $response = json_decode($response->getBody(), true);
            $status = $response['status'] ?? '';
            if ($code != 200) {
                dump("微信退款未知原因！订单号：{$orderSn} 错误原因：" . json_encode($response));exit;
            }
            return [1, $response];
        } catch (\Exception $e) {
            // 异常错误处理
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $code = $r->getStatusCode();
                $response = json_decode($r->getBody(), true);
                $subCode = $response['code'] ?? '';
                $subMsg = $response['message'] ?? '';
                $listErrorCode = [
                    'NOT_ENOUGH',  //	余额不足',
                    'MCH_NOT_EXISTS',  //MCHID不存在',
                    'NO_AUTH',  //没有退款权限',
                ];
                if (in_array($subCode, $listErrorCode)) {
                    dump("微信退款失败！\n订单号：{$orderSn} \n错误原因：" . $subMsg);exit;
                }
                return [$code, $response];
            }
        }
        return [0, '退款失败'];
    }

    /**
     * 回调
     * @param $header
     * @param $inBody
     * @return array|string[]|void
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function notify($header, $inBody)
    {
        $inWechatpaySignature = $header['wechatpay-signature'];
        $inWechatpayTimestamp = $header['wechatpay-timestamp'];
        $inWechatpaySerial = $header['wechatpay-serial'];
        $inWechatpayNonce = $header['wechatpay-nonce'];
        if (!$inWechatpaySignature or !$inWechatpayTimestamp or !$inWechatpaySerial or !$inWechatpayNonce) {
            header("Location:/404.html");
            exit;
        }
        $wxPayConfig = config('wx_pay_config');
        $merchantId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $apiv3Key = $wxPayConfig['api_v3_key'] ?? '';// 在商户平台上设置的APIv3密钥
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $merchantId;
        // 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
        $platformCertificateFilePath = $certPath . '/cert.pem';
        // 根据通知的平台证书序列号，查询本地平台证书文件，这里是自己生成的证书
        $platformPublicKeyInstance = Rsa::from(file_get_contents($platformCertificateFilePath), Rsa::KEY_TYPE_PUBLIC);
        // 检查通知时间偏移量，允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );
        if ($timeOffsetStatus && $verifiedStatus) {
            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);
            // 使用PHP7的数据解构语法，从Array中解构并赋值变量
            ['resource' => [
                'ciphertext' => $ciphertext,
                'nonce' => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;
            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
            // 把解密后的文本转换为PHP Array数组
            return (array)json_decode($inBodyResource, true);
        } else {
            return ['trade_state' => 'FAIL'];
        }
    }


    /**
     * 获取平台证书列表
     * @return void
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function getCert()
    {
        // 设置参数
        [$instance, $wxPayConfig] = self::init();
        $api_v3_key = $wxPayConfig['api_v3_key'] ?? '';   // 「商户API证书」的「证书序列号」
        // 发送请求
        $resp = $instance->chain('v3/certificates')->get(
//            ['debug' => true] // 调试模式，https://docs.guzzlephp.org/en/stable/request-options.html#debug
        );
        $result = json_decode($resp->getBody(), true);
        $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? '';
        $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? '';
        $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? '';
        $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data);
        $result['ciphertext_msg'] = $decryptedMsg;
        dump($result);
    }

    /**
     * 获取证书
     * @return mixed
     */
    public static function certificates()
    {
        $wxPayConfig = config('wx_pay_config');
        $api_v3_key = $wxPayConfig['api_v3_key'] ?? '';     // 商户号
        $result = self::wx_curl_query('v3/certificates','GET');
        $result = json_decode($result, true);
        $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? '';
        $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? '';
        $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? '';
        $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data);
        $result['decryptedMsg'] = ($decryptedMsg); //解密后的内容，就是证书内容
        dump($result);
    }

    //get请求
    public static function wx_curl_query($urlV3 = '', $http_method = 'POST', $body = '')
    {
        $url = 'https://api.mch.weixin.qq.com/' . $urlV3;
        $config = config('wx_pay_config');;
        $mchId = $config['mchid'] ?? ''; // 商户ID
        $serial_no = $config['cert_serial'] ?? ''; // 证书序列号
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId;
        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem';
        $mch_private_key = openssl_get_privatekey(file_get_contents($merchantPrivateKeyFilePath));;//私钥
        if(is_array($body)){
            $body = json_encode($body); // 接口参数
        }
        // 生成token 验签
        $timestamp = time();//时间戳
        $nonce = Formatter::nonce(32);//随机串
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //构造签名串
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";//报文主体
        //计算签名值
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        //设置HTTP头
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $mchId, $nonce, $timestamp, $serial_no, $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: ' . $token,
            "Wechatpay-Serial:{$serial_no}"
        ];

        $info = curl_init();
        curl_setopt($info, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($info, CURLOPT_HEADER, 0);
        curl_setopt($info, CURLOPT_NOBODY, 0);
        curl_setopt($info, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($info, CURLOPT_SSL_VERIFYHOST, false);
        if ($http_method == 'POST') {
            curl_setopt($info, CURLOPT_POST, 1);
            curl_setopt($info, CURLOPT_POSTFIELDS, json_encode($body));
        }
        //设置header头
        curl_setopt($info, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($info, CURLOPT_URL, $url);
        $output = curl_exec($info);
        curl_close($info);
        return $output;
    }

}
